﻿using System;
using System.Collections.Generic;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using PoliticalClassifier.Application;
using PoliticalClassifier.Domain;
using PoliticalClassifier.Support;
using PoliticalClassifier.Web.Models;

namespace PoliticalClassifier.Web.Controllers
{
	/// <summary>
	/// The main controller for the Political Classifier MVC application.
	/// </summary>
	/// <remarks>
	///		<para>This controller controls the main flow of the Political Classifier MVC application.  The primary control flow is as follows:</para>
	///		<list type="number">
	///			<item>
	///				<description>Display the LoadFile view for uploading a data file.</description>
	///			</item>
	///			<item>
	///				<description>Display the blank DisplayResults view for setting simulation weights and parties.</description>
	///			</item>
	///			<item>
	///				<description>Display the DisplayResults view with the current simulation settings and the results of the simulation.</description>
	///			</item>
	///		</list>
	/// </remarks>
	public class MainController : Controller
	{
		/// <summary>
		/// Displays the LoadFile view.
		/// </summary>
		/// <remarks>
		///		<para>This action displays a view that allows the user to choose a data file on his local machine to upload to the server.  The view is bound to a <see cref="LoadFileViewModel" /> for uploading the data file.</para>
		///		<para>Preconditions:</para>
		///		<list type="bullet">
		///			<item>
		///				<description>None</description>
		///			</item>
		///		</list>
		///		<para>Postconditions:</para>
		///		<list type="bullet">
		///			<item>
		///				<description>None</description>
		///			</item>
		///		</list>
		/// </remarks>
		/// <returns>The LoadFile view.</returns>
		[HttpGet]
		public ActionResult LoadFile()
		{
			Session.Clear();
			return View(new LoadFileViewModel());
		}


		/// <summary>
		/// Displays the LoadFile view with errors, or redirects to DisplayResults.
		/// </summary>
		/// <remarks>
		///		<para>This action validates and processes the posted data file.  If there are any errors in the data file, the LoadFile view is redisplayed with a list of all errors.  If the data file is error-free, the user is redirected to <see cref="M:PoliticalClassifier.Web.Controllers.MainController.DisplayResults" />.</para>
		///		<para>Preconditions:</para>
		///		<list type="bullet">
		///			<item>
		///				<description>None</description>
		///			</item>
		///		</list>
		///		<para>Postconditions:</para>
		///		<list type="bullet">
		///			<item>
		///				<description>Data file is stored on server.</description>
		///			</item>
		///			<item>
		///				<description>A <see cref="PoliticalClassifierModeler" /> is created for running the simulation.</description>
		///			</item>
		///			<item>
		///				<description>The data file is loaded and processed by the <see cref="PoliticalClassifierModeler" />.</description>
		///			</item>
		///			<item>
		///				<description>The <see cref="PoliticalClassifierModeler" /> is stored in the Session under the key <c>Modeler</c>.</description>
		///			</item>
		///		</list>
		/// </remarks>
		/// <param name="model">A <see cref="LoadFileViewModel" /> with the posted file.</param>
		/// <returns>The LoadFile view or a redirect to DisplayResults.</returns>
		[HttpPost]
		public ActionResult LoadFile(LoadFileViewModel model)
		{
			// Validate uploaded file model
			if (!ModelState.IsValid)
				return View(model);

			string path = Path.Combine(Server.MapPath("~/App_Data/Uploads/"), Session.SessionID + Path.GetExtension(model.File.FileName));

			// Delete existing data file for current session
			if (System.IO.File.Exists(path))
				System.IO.File.Delete(path);

			model.File.SaveAs(path);
			PoliticalClassifierModeler modeler = new PoliticalClassifierModeler();
			Session["FileName"] = modeler.FileName = path;
			IDataSourceLoadResults loadResults = modeler.Load();

			// Check for and report any errors in data file
			foreach (IDataSourceLoadError error in loadResults.GetErrors())
				ModelState.AddModelError("File", error.GetErrorMessage());

			// Display results when data file is error-free
			if (loadResults.CanContinue)
			{
				Session["Modeler"] = modeler;
				return RedirectToAction("DisplayResults");
			}

			return View(model);
		}


		/// <summary>
		/// Displays a message that the uploaded file was too large on the LoadFile view.
		/// </summary>
		/// <remarks>
		///		<para>If the user attempts to upload a file that is larger than the request length limit, an exception is thrown.  The application then discards this exception and transfers execution into this action.  This action re-displays the LoadFile view to the user, along with a message indicating that the previously posted file was too large.</para>
		///		<para>Preconditions:</para>
		///		<list type="bullet">
		///			<item>
		///				<description>None</description>
		///			</item>
		///		</list>
		///		<para>Postconditions:</para>
		///		<list type="bullet">
		///			<item>
		///				<description>None</description>
		///			</item>
		///		</list>
		/// </remarks>
		/// <returns>The LoadFile view.</returns>
		public ActionResult FileTooBig()
		{
			ModelState.AddModelError("File", "The chosen file was too large. Please choose a file under 4MB.");
			return View("LoadFile", new LoadFileViewModel());
		}


		/// <summary>
		/// Displays the DisplayResults view for collecting simulation settings.
		/// </summary>
		/// <remarks>
		///		<para>This action expects the <see cref="PoliticalClassifierModeler" /> to be stored in the Session.  If the <see cref="PoliticalClassifierModeler" /> is not found in the Session, the user is shown the LoadFile view along with a message indicating that his session had expired.</para>
		///		<para>If the <see cref="PoliticalClassifierModeler" /> is found in the Session, this action displays a view that allows the user to set voter attribute weights and party coordinates for the simulation.  This view will also display any warning messages concerning the processing of the data file, such as discarded rows.  The view is bound to a <see cref="DisplayResultsViewModel" /> for transfering the simulation settings.</para>
		///		<para>Preconditions:</para>
		///		<list type="bullet">
		///			<item>
		///				<description>Data file is stored on server.</description>
		///			</item>
		///			<item>
		///				<description>The data file is loaded and processed by the <see cref="PoliticalClassifierModeler" />.</description>
		///			</item>
		///			<item>
		///				<description>The <see cref="PoliticalClassifierModeler" /> is stored in the Session under the key <c>Modeler</c>.</description>
		///			</item>
		///		</list>
		///		<para>Postconditions:</para>
		///		<list type="bullet">
		///			<item>
		///				<description>Data file is still stored on server.</description>
		///			</item>
		///			<item>
		///				<description>The <see cref="PoliticalClassifierModeler" /> is still stored in the Session under the key <c>Modeler</c>.</description>
		///			</item>
		///		</list>
		/// </remarks>
		/// <returns>The DisplayResults view.</returns>
		[HttpGet]
		public ActionResult DisplayResults()
		{
			PoliticalClassifierModeler modeler = Session["Modeler"] as PoliticalClassifierModeler;

			// Verify modeler contained within session
			if (modeler == null)
			{
				ModelState.AddModelError("File", "Your session has expired. Please start over by uploading a file.");
				return View("LoadFile", new LoadFileViewModel());
			}

			// Verify file is loaded, check for and report any warnings related to data file
			if (modeler.LastLoadResults != null)
				foreach (IDataSourceLoadWarning warning in modeler.LastLoadResults.GetWarnings())
					ModelState.AddModelError("File", warning.GetWarningMessage());

			return View(new DisplayResultsViewModel() { Weights = modeler.ColumnNames.Where(c => c != "id").Select(c => new PointViewModel() { Name = c, X = 0, Y = 0 }).ToArray(), Candidates = Enumerable.Range(1, 2).Select(c => new PointViewModel() { Name = string.Format("C{0}", c), X = 0, Y = 0 }).ToArray() });
		}


		/// <summary>
		/// Displays the DisplayResults view for collecting simulation settings.
		/// </summary>
		/// <remarks>
		///		<para>This action expects the <see cref="PoliticalClassifierModeler" /> to be stored in the Session.  If the <see cref="PoliticalClassifierModeler" /> is not found in the Session, the user is shown the LoadFile view along with a message indicating that his session had expired.</para>
		///		<para>If the <see cref="PoliticalClassifierModeler" /> is found in the Session, this action displays a view that allows the user to set voter attribute weights and party coordinates for the simulation.  This view will also display the results of a successfully run simulation or any errors in the submitted simulation settings.  The view is bound to a <see cref="DisplayResultsViewModel" /> for transfering the simulation settings and results.</para>
		///		<para>Preconditions:</para>
		///		<list type="bullet">
		///			<item>
		///				<description>Data file is stored on server.</description>
		///			</item>
		///			<item>
		///				<description>The data file is loaded and processed by the <see cref="PoliticalClassifierModeler" />.</description>
		///			</item>
		///			<item>
		///				<description>The <see cref="PoliticalClassifierModeler" /> is stored in the Session under the key <c>Modeler</c>.</description>
		///			</item>
		///		</list>
		///		<para>Postconditions:</para>
		///		<list type="bullet">
		///			<item>
		///				<description>Data file is still stored on server.</description>
		///			</item>
		///			<item>
		///				<description>The <see cref="PoliticalClassifierModeler" /> is still stored in the Session under the key <c>Modeler</c>.</description>
		///			</item>
		///			<item>
		///				<description>Simulation weights have been set in <see cref="PoliticalClassifierModeler" />.</description>
		///			</item>
		///			<item>
		///				<description>Simulation parties have been set in <see cref="PoliticalClassifierModeler" />.</description>
		///			</item>
		///			<item>
		///				<description>Simulation has been run using <see cref="PoliticalClassifierModeler" />.</description>
		///			</item>
		///		</list>
		/// </remarks>
		/// <param name="model">A <see cref="DisplayResultsViewModel" /> containing the simulation settings.</param>
		/// <returns>The DisplayResults view.</returns>
		[HttpPost]
		public ActionResult DisplayResults(DisplayResultsViewModel model)
		{
			PoliticalClassifierModeler modeler = Session["Modeler"] as PoliticalClassifierModeler;

			// Verify modeler contained within session
			if (modeler == null)
			{
				ModelState.AddModelError("File", "Your session has expired. Please start over by uploading a file.");
				return View("LoadFile", new LoadFileViewModel());
			}

			// Validate model with simulation settings
			if (ModelState.IsValid)
			{
				// Set simulation weight values in modeler
				foreach (var weight in model.Weights)
					modeler.WeightManager.AddWeight(new WeightAttribute(weight.Name, new Point2D(weight.X.Value, weight.Y.Value)));

				// Set party coordinates in modeler
				foreach (var candidate in model.Candidates)
					modeler.CandidateManager.AddCandidate(new Candidate() { Name = candidate.Name, Location = new Point2D(candidate.X.Value, candidate.Y.Value) });

				model.Results = modeler.Compute();
				Session["Modeler"] = modeler;
			}

			return View(model);
		}


		/// <summary>
		/// Generates a graphical plot image.
		/// </summary>
		/// <remarks>
		///		<para>Preconditions:</para>
		///		<list type="bullet">
		///			<item>
		///				<description>Data file has been uploaded to server.</description>
		///			</item>
		///			<item>
		///				<description><see cref="PoliticalClassifierModeler" /> has been stored in the Session object with the key <c>Modeler</c>.</description>
		///			</item>
		///			<item>
		///				<description>Simulation weights have been set in <see cref="PoliticalClassifierModeler" />.</description>
		///			</item>
		///			<item>
		///				<description>Simulation has been run using <see cref="PoliticalClassifierModeler" />.</description>
		///			</item>
		///		</list>
		///		<para>Postconditions:</para>
		///		<list type="bullet">
		///			<item>
		///				<description>Data file remains stored on server.</description>
		///			</item>
		///			<item>
		///				<description>The <see cref="PoliticalClassifierModeler" /> remains stored in the Session object with the key <c>Modeler</c>.</description>
		///			</item>
		///		</list>
		/// </remarks>
		/// <returns>A <see cref="System.Web.Mvc.FileContentResult" /> object containing the binary data for the plotted graph image.</returns>
		public ActionResult DisplayPlot()
		{
			PoliticalClassifierModeler modeler = Session["Modeler"] as PoliticalClassifierModeler;

			// Verify modeler contained within session
			if ((modeler == null) || (modeler.LastSimulationResults == null))
				return HttpNotFound();

			// Plot graph and save it to the output stream
			using (MemoryStream plotStream = new MemoryStream())
			{
				modeler.PlotGraph(modeler.LastSimulationResults).Save(plotStream, ImageFormat.Png);
				return File(plotStream.ToArray(), "image/png", string.Format("{0}.png", Session.SessionID));
			}
		}
	}
}